local type,tostring,select,pairs,ipairs,assert=type,tostring,select,pairs,ipairs,assert
local string_len,string_rep,string_match,string_format,string_find,string_byte=
	  string.len,string.rep,string.match,string.format,string.find,string.byte
local table_insert,table_remove=
	  table.insert,table.remove
module(..., package.seeall)

require'zdc.logger'
require'zdc.fieldutil'
require'zdc.dpl'
require'zdc.nodetree'

local dfskey=zdc.fieldutil.dfskey

DplTree = {}
for _i,_j in pairs(zdc.dpl.Dpl) do
	DplTree[_i]=_j
end
local DplTree_mt = {__metatable={}, __index=DplTree}


function new(dal,db_module, db_params, prototype_id, nodenew, sandboxenv, secugroups,auth,logger,childmt)
	nodenew = nodenew or zdc.nodetree.new
	local self=zdc.dpl.new(dal,db_module, db_params, prototype_id, nodenew, sandboxenv, secugroups,auth,logger,childmt or DplTree_mt)
	return self
end

function DplTree:_has_node(id)
	if self._cache[id] then return true end
end
local function checkautocfg(cfd,k)
	for i=1,#cfd do
		if string.match(k,cfd[i]) then return true end
	end
end
function DplTree:_get_node(id)
	if not id then return nil,'nil id' end
	local cache_key = id
	local _sca = self._cache
	if _sca[cache_key] then
		return _sca[cache_key]
	end

	if not self._nouse_parent_cfd then
		local parent_id, rest = string.match(id, "^(.+)/(.-)$")
		if parent_id then
			local parent = self.dal:GetNode(parent_id)
			if parent then
				local cfd,cad=parent._cfd,parent._cad
				if (cfd and cfd[rest]) or (cad and checkautocfg(cad,rest))then
					local node,errmsg = parent:__make_child(rest,mc)
					if node then
						_sca[cache_key] = node
						return node,errmsg
					end
					if errmsg then print(errmsg) end
				end
			end
		end
	end
	if self._get_fallback_node then
		local prototype = nil
		local node, stub = self:_get_fallback_node(id)
		if not stub then
			_sca[cache_key] = node
		end
		return node,stub
	end
end

function DplTree:_sure_node(id,mc,nocache)
	local _sca = self._cache
	local t=_sca[id]
	if t then return t end
	
	local parent_id, rest = string.match(id, "^(.+)/(.-)$")
	if parent_id then
		local parent = self.dal:SureNode(parent_id)
		if parent then
			local node,errmsg = parent:__make_child(rest,mc)
			if node then
				if not nocache then _sca[id] = node end
				return node,errmsg
			end
			if errmsg then print(errmsg) end
		end
	end

	if self._get_fallback_node then
		local prototype = nil
		local node, stub = self:_get_fallback_node(id)
		if not (nocache or stub)then _sca[id] = node end
		return node
	end
end

function DplTree:_get_child_nodes(parentid,cn)
	cn=cn or {}
	local par=self._cache[parentid]
	if par then
		par:__get_child_all(cn)
	end
	return cn
end

function DplTree:_get_child_ids_ex(parentid,where,sort,limit)
	error'notimpl'
end

function DplTree:_get_prefix_ids_ex(prefix,where,sort,limit)
	error'notimpl'
end

function DplTree:_get_child_cnt_ex(parentid,where)
	error'notimpl'
end

function DplTree:_get_prefix_cnt_ex(prefix,where)
	error'notimpl'
end


local function _loadnodeinit(dt,ch,id,node,dfn,bad)
	if node and not dt[id] then
		dt[id] = true
		local _pto = node._pto or dfn
		if not dt[_pto] then 
			_loadnodeinit(dt,ch,_pto,ch[_pto],dfn,bad)
		end
		if not pcall(node.__do_inh_act,node) then bad[id]=true end
	end
end

local function _pre_install_child(ch,precid,idx,parent)
	local nodes = {}
	local len=#parent+1
	for _i = idx,#precid do
		local _id = precid[_i]
		if string.sub(_id,1,#parent)~= parent or string.byte(_id,len) ~= 47 or string.find(_id,'/',len+1) then
			return nodes,_i
		end
		nodes[_id]=ch[_id]
	end
	return nodes
end

function DplTree:BuildDbSkl()
	local data = self._db:get_node(self._treeid)
	if data then
		return 
	else
		print('build db root skl')
		rs,errmsg = assert(self:_get_node(self._treeid))
		self._db:transdo(function() rs:__save(true) end)
		return true
	end
end

--TODO:TREE
function DplTree:_put_cache_tree(node)
	local _ca,sn=self._cache,node._subnode
	_ca[node.__id]=node
	if sn then
		for _i,_j in pairs(sn) do
			self:_put_cache_tree(_j)
		end
	end
end

function DplTree:_reset_cache(force)
	if force or not self._no_reset_cache then
		self._cache = {}
	end
	if force then
		if self._db.reset_cache then
			self._db:reset_cache()
		end
	end
	local precid = {}
	local cache = self._cache 
	local _treeid = (#self._treeid>1 and self._treeid .. '/') or ''
	--local db_nodes = self:_get_db_nodes_by_prefix(_treeid)
	local db_nodes,lst=self._db:get_nodes_by_prefix_ex(_treeid)
	local rs,errmsg
	if #self._treeid>0 then
		local dbn = self._db:get_node(self._treeid)
		if dbn then
			rs,errmsg = self:_make_node(self._treeid,dbn.data,dbn.ik,dbn.ts,false,true)
		else
			rs,errmsg = self:_get_node(self._treeid)
		end
		cache[self._treeid]=rs
		if not rs then print(errmsg) end
		precid[#precid+1]=self._treeid
	end
	for id, vnode in pairs(db_nodes) do
		rs,errmsg = self:_make_node(id, vnode.data,vnode.ik,vnode.ts,false,true)
		if rs then
			cache[id]=rs
			precid[#precid+1]=id
		else
			print(errmsg)
		end
	end
	table.sort(precid,zdc.idutil.CmpSub)
	local doneid = {}
	local bad = {}
	for _,id in pairs(precid) do
		local node = cache[id]
		_loadnodeinit(doneid,cache,id,node,self._proto_id,bad)
	end
	for k,v in pairs(bad) do cache[k]=nil end
	local _ci,_ns=1
	for _ii,id in ipairs(precid) do
		if _ci <= _ii then _ci = _ii+1 end
		_ns,_ci=_pre_install_child(cache,precid,_ci,id)
		local node = cache[id]
		if node then
			node:__load_child(nil,_ns)
		end
		if not _ci then
			break
		end
	end
	if cache[self._treeid] then
		self:_put_cache_tree(cache[self._treeid])
	end
	self:BuildDbSkl()
end

function DplTree:_drop_all(opt)
	local label=opt and opt.label
	local nl
	self._db:transdo(function()
		self:_remove_node(self._treeid,true)
		nl=odc.dblabel.label_inc(self._db,self._treeid,nil,label)
	end)
	self:_reset_cache(true)
	for k,v in pairs(self._trees) do
		k:_reset_cache(true)
	end
	--self:Reset()
	local rec=zdc.chglog.new()
	rec._drop_all=true
	rec.label=nl
	self.dal:_post_chglist(self,rec,rec)
	return nl
end